Analyse des données DVF

Arrondissements de paris

Elias TOURNEUX et Yann VILLELLAS TD I, étudiant à l'ESILV en A3, promo 2025

Mini-rapport :

Avancement et difficultés :

Nous avons eu beaucoup de problèmes lors du tri des données. Certaines valeurs étaient faussés, comme par exemple la surface inférieur à 1 m² par exemple, ou encore des noms de ville avec un tiret parfois sans espace, parfois avec un espace. Nous avons donc du faire beaucoup de tests pour pouvoir trier les données correctement. Nous avons aussi eu des problèmes avec les graphiques, car nous n'avions pas compris comment les utiliser. Nous avons donc du faire des recherches pour pouvoir les utiliser correctement. Nous avons souhaités faire des cartes dynamiques à l'aide de Folium, et cela nous a aussi pris beaucoup de temps pour comprendre le concept de geojson.

Ratio de contribution:

  • Elias TOURNEUX : 50 %
  • Yann VILLELLAS : 50 %

Introduction

Import des différents modules utiles au projet

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import folium
from IPython.display import IFrame
import requests
import matplotlib.cm as cm

Import des données de 2019 et 2022 depuis l'Open Data du gouvernement

dvf2022 = pd.read_csv("valeursfoncieres-2022.txt", sep="|", low_memory=False)
dvf2019 = pd.read_csv("valeursfoncieres-2019.txt", sep="|", low_memory=False)

On peut observer la nature de nos données

On observe que les données sont de type DataFrame, et qu'elles sont composées de 3803885 lignes et de 43 colonnes pour les valeurs de 2022. Le jeu de données de 2019 est quasiment identique, et possède les mêmes en-têtes.

On va supprimer les colonnes qui ne nous intéressent pas pour notre analyse.

dvf2022
Identifiant de document Reference document 1 Articles CGI 2 Articles CGI 3 Articles CGI 4 Articles CGI 5 Articles CGI No disposition Date mutation Nature mutation ... Surface Carrez du 5eme lot Nombre de lots Code type local Type local Identifiant local Surface reelle bati Nombre pieces principales Nature culture Nature culture speciale Surface terrain
0 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2022 Vente ... NaN 1 2.0 Appartement NaN 24.0 1.0 NaN NaN NaN
1 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2022 Vente ... NaN 0 NaN NaN NaN NaN NaN S NaN 84.0
2 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2022 Vente ... NaN 0 NaN NaN NaN NaN NaN S NaN 88.0
3 NaN NaN NaN NaN NaN NaN NaN 1 03/01/2022 Vente ... NaN 1 2.0 Appartement NaN 140.0 3.0 NaN NaN NaN
4 NaN NaN NaN NaN NaN NaN NaN 1 04/01/2022 Vente ... NaN 0 NaN NaN NaN NaN NaN T NaN 510.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3803880 NaN NaN NaN NaN NaN NaN NaN 1 30/12/2022 Vente ... NaN 1 4.0 Local industriel. commercial ou assimilé NaN 327.0 0.0 NaN NaN NaN
3803881 NaN NaN NaN NaN NaN NaN NaN 1 19/12/2022 Vente ... NaN 2 3.0 Dépendance NaN 0.0 0.0 NaN NaN NaN
3803882 NaN NaN NaN NaN NaN NaN NaN 1 19/12/2022 Vente ... NaN 2 2.0 Appartement NaN 40.0 3.0 NaN NaN NaN
3803883 NaN NaN NaN NaN NaN NaN NaN 1 30/12/2022 Vente ... NaN 1 3.0 Dépendance NaN 0.0 0.0 NaN NaN NaN
3803884 NaN NaN NaN NaN NaN NaN NaN 1 21/11/2022 Vente ... NaN 1 3.0 Dépendance NaN 0.0 0.0 NaN NaN NaN

3803885 rows × 43 columns

Nettoyage des données

En effet, nous avons remarqué que certaines données étaient manquantes ou encore inutiles dans notre analyse (comme les références des articles, documents, etc...) nous avons donc décidé de les supprimer.

Regardons la part de nos données qui sont nuls

dvf2022.isnull().sum()/len(dvf2022)*100
Identifiant de document       100.000000
Reference document            100.000000
1 Articles CGI                100.000000
2 Articles CGI                100.000000
3 Articles CGI                100.000000
4 Articles CGI                100.000000
5 Articles CGI                100.000000
No disposition                  0.000000
Date mutation                   0.000000
Nature mutation                 0.000000
Valeur fonciere                 0.649888
No voie                        34.733621
B/T/Q                          95.374019
Type de voie                   36.984294
Code voie                       1.026398
Voie                            1.029290
Code postal                     1.029816
Commune                         0.000000
Code departement                0.000000
Code commune                    0.000000
Prefixe de section             95.824295
Section                         0.004075
No plan                         0.000000
No Volume                      99.765792
1er lot                        66.631142
Surface Carrez du 1er lot      90.332568
2eme lot                       89.535278
Surface Carrez du 2eme lot     96.741752
3eme lot                       98.134670
Surface Carrez du 3eme lot     99.635662
4eme lot                       99.402611
Surface Carrez du 4eme lot     99.911880
5eme lot                       99.744130
Surface Carrez du 5eme lot     99.967481
Nombre de lots                  0.000000
Code type local                39.918478
Type local                     39.918478
Identifiant local             100.000000
Surface reelle bati            39.973396
Nombre pieces principales      39.973396
Nature culture                 33.830912
Nature culture speciale        95.934262
Surface terrain                33.830912
dtype: float64

On peut observer que certaines colonnes sont composées de beaucoup ou presque que des valeurs nulles comme : 3ème lot, 4ème lot, 5ème lot et leurs surfaces Carrez associé. Cela signifie qu'on a une grande proportionnalité de valeurs foncières avec un ou deux lots. On remarque qu'un tiers des valeurs foncières correspondent à des terrains.

Observons les différents types de nature de mutation

type_nature = dvf2022['Nature mutation'].unique()
# Pourcentage de chaque type de mutation
for i in type_nature:
    print(i, ":", len(dvf2022[dvf2022['Nature mutation'] == i])/len(dvf2022)*100)

# On ne garde que les mutations de type "vente" et "vente en l'état futur d'achèvement"
dvf2022 = dvf2022[dvf2022['Nature mutation'].isin(["Vente", "Vente en l'état futur d'achèvement"])]
Vente : 92.60445570778296
Vente en l'état futur d'achèvement : 6.041507563977355
Echange : 0.888354931865711
Vente terrain à bâtir : 0.30968338948206897
Adjudication : 0.13799050181590664
Expropriation : 0.018007905075994673

On observe que le type "Vente" et "Vente en l'état futur d'achèvement" sont les plus représentés dans notre jeu de données. On peut donc en déduire que les ventes sont les plus fréquentes. Nous alloons donc nous concentrer sur ces deux types de mutations, étant donné qu'ils représentent plus de 99% de nos données.

#Suppression des colonnes inutiles pour les demandes des valeurs foncières de 2022
dvf2022 = dvf2022.drop(['Identifiant de document', 'Reference document', '1 Articles CGI', '2 Articles CGI', '3 Articles CGI', '4 Articles CGI','5 Articles CGI', 'No disposition', 'No voie'], axis=1)
dvf2022 = dvf2022.drop(['B/T/Q', 'Type de voie', 'Code voie', 'Code commune', 'Prefixe de section', 'Section', 'No plan', 'No Volume'], axis=1)
dvf2022 = dvf2022.drop(['Voie','Nature culture speciale', 'Identifiant local', 'Nombre de lots', '3eme lot', 'Surface Carrez du 3eme lot', '5eme lot', 'Surface Carrez du 5eme lot', '4eme lot', 'Surface Carrez du 4eme lot'], axis=1)

#blabla loi carrez supprimé surface batti psk on a pas besoin de ça

#Même chose pour les demandes des valeurs foncières de 2019
dvf2019 = dvf2019.drop(['Identifiant de document', 'Reference document', '1 Articles CGI', '2 Articles CGI', '3 Articles CGI', '4 Articles CGI','5 Articles CGI', 'No disposition', 'No voie'], axis=1)
dvf2019 = dvf2019.drop(['B/T/Q', 'Type de voie', 'Code voie', 'Code commune', 'Prefixe de section', 'Section', 'No plan', 'No Volume'], axis=1)
dvf2019 = dvf2019.drop(['Voie','Nature culture speciale', 'Identifiant local', 'Nombre de lots', '3eme lot', 'Surface Carrez du 3eme lot', '5eme lot', 'Surface Carrez du 5eme lot', '4eme lot', 'Surface Carrez du 4eme lot'], axis=1)

#ValueError: could not convert string to float: '55000,00'
dvf2022['Valeur fonciere'] = dvf2022['Valeur fonciere'].str.replace(',', '.').astype(float)
dvf2019['Valeur fonciere'] = dvf2019['Valeur fonciere'].str.replace(',', '.').astype(float)

#On avait une erreur vu qu'on pouvait pas calculer sur des strings
dvf2022['Surface reelle bati'] = dvf2022['Surface reelle bati'].astype(float)
dvf2019['Surface reelle bati'] = dvf2019['Surface reelle bati'].astype(float)

#On créé une nouvelle colonne qui contient le prix au mètre carré qui est la sormme des Surface Carrez
dvf2022['Surface Carrez du 1er lot'] = dvf2022['Surface Carrez du 1er lot'].str.replace(',', '.').astype(float)
dvf2022['Surface Carrez du 2eme lot'] = dvf2022['Surface Carrez du 2eme lot'].str.replace(',', '.').astype(float)

#On remplace toutes les valeurs nulles par 0
dvf2022 = dvf2022.fillna(0)
dvf2019 = dvf2019.fillna(0)

dvf2022['Metre carre'] = dvf2022['Surface Carrez du 1er lot'].astype(float) + dvf2022['Surface Carrez du 2eme lot'].astype(float)

#Enleve les valeurs en double
dvf2022 = dvf2022.drop_duplicates(subset=['Date mutation', 'Valeur fonciere', 'Surface reelle bati', 'Metre carre'], keep='first')

# On fait un nouveau tableau contenant que des "Metre carre" non nuls
dvf2022_metre_carre = dvf2022[dvf2022['Metre carre'] >= 1]

#On remplace le nom des communes avec un - par un espace
dvf2022_metre_carre['Commune'] = dvf2022_metre_carre['Commune'].str.replace('-', ' ')


#On affiche les deux tableaux
dvf2022
Date mutation Nature mutation Valeur fonciere Code postal Commune Code departement 1er lot Surface Carrez du 1er lot 2eme lot Surface Carrez du 2eme lot Code type local Type local Surface reelle bati Nombre pieces principales Nature culture Surface terrain Metre carre
0 03/01/2022 Vente 55000.0 1000.0 BOURG-EN-BRESSE 01 7 24.10 0 0.0 2.0 Appartement 24.0 1.0 0 0.0 24.10
1 03/01/2022 Vente 143000.0 1480.0 SAVIGNEUX 01 0 0.00 0 0.0 0.0 0 0.0 0.0 S 84.0 0.00
3 03/01/2022 Vente 143000.0 1480.0 SAVIGNEUX 01 1 123.23 0 0.0 2.0 Appartement 140.0 3.0 0 0.0 123.23
4 04/01/2022 Vente 300.0 1480.0 MESSIMY SUR SAONE 01 0 0.00 0 0.0 0.0 0 0.0 0.0 T 510.0 0.00
5 06/01/2022 Vente 255000.0 1560.0 MANTENAY-MONTLIN 01 0 0.00 0 0.0 1.0 Maison 108.0 5.0 S 649.0 0.00
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
3803870 29/12/2022 Vente 1650000.0 75016.0 PARIS 16 75 105 154.75 150 0.0 2.0 Appartement 150.0 6.0 0 0.0 154.75
3803880 30/12/2022 Vente 3500000.0 75014.0 PARIS 14 75 3 0.00 0 0.0 4.0 Local industriel. commercial ou assimilé 327.0 0.0 0 0.0 0.00
3803881 19/12/2022 Vente 525000.0 75015.0 PARIS 15 75 49 46.65 50 0.0 3.0 Dépendance 0.0 0.0 0 0.0 46.65
3803882 19/12/2022 Vente 525000.0 75015.0 PARIS 15 75 49 46.65 50 0.0 2.0 Appartement 40.0 3.0 0 0.0 46.65
3803883 30/12/2022 Vente 15500.0 75013.0 PARIS 13 75 244 0.00 0 0.0 3.0 Dépendance 0.0 0.0 0 0.0 0.00

1671525 rows × 17 columns

On remarque qu'après le traitement, nous avons perdus METTRE LE NBRE DE COLONNES ET LIGNES Note : quand on fait

Suppression des lignes avec des valeurs manquantes

dvf2022 = dvf2022.dropna() dvf2019 = dvf2019.dropna()

ça enlève toute la ligne enft

Les valeurs infinies

Lors de notre analyse, nous avons remarqué que certaines valeurs étaient infinies. Pour palier à ça, nous avons remarqué que cela occurait lorsque les valeurs foncières valaient 0. On remarque donc qu'il y a 1830 dons vu que la valeur foncière est égale à 0. Nous avons donc décidé de supprimer ces valeurs.

dvf2022[dvf2022['Valeur fonciere'] <= 0]

#On supprime les valeurs négatives
dvf2022 = dvf2022[dvf2022['Valeur fonciere'] > 0]

Début de l'analyse de notre jeu de données

1. Quel département a le prix moyen au mètre carré le plus élevé ?

prix_m2_departement_2022 = dvf2022_metre_carre.groupby('Code departement')['Valeur fonciere'].mean() / dvf2022_metre_carre.groupby('Code departement')['Metre carre'].mean()

#On affiche
prix_m2_departement_2022.idxmax()
'75'

2. Quel département a le prix moyen du terrain au mètre carré le plus élevé ?

prix_m2_terrain_departement_2022 = dvf2022.groupby('Code departement')['Valeur fonciere'].mean() / dvf2022.groupby('Code departement')['Surface terrain'].mean()

#On affiche
prix_m2_terrain_departement_2022.idxmax()
'75'

3. Représentation graphique du prix moyen au mètre carré par département

# Graphique du prix moyen au mètre carré par département en fonction de la surface du terrain
fig, ax = plt.subplots()
prix_m2_terrain_departement_2022.plot(kind='bar', x='Code departement', y='Prix moyen au mètre carré')
ax.set_xlabel('Département')
ax.set_ylabel('Prix moyen au mètre carré')
ax.set_title('Prix moyen au mètre carré par département en fonction de la surface du terrain')
for i, v in enumerate(prix_m2_terrain_departement_2022):
    ax.text(i-0.3, v+10, str(prix_m2_terrain_departement_2022.index[i]), rotation=90)

# Graphique du prix moyen au mètre carré par département en fonction de la surface bâtie
fig, ax = plt.subplots()
prix_m2_departement_2022.plot(kind='bar', x='Code departement', y='Prix moyen au mètre carré')
ax.set_xlabel('Département')
ax.set_ylabel('Prix moyen au mètre carré')
ax.set_title('Prix moyen au mètre carré par département en fonction de la surface bâtie')
for i, v in enumerate(prix_m2_departement_2022):
    ax.text(i-0.3, v+10, str(prix_m2_departement_2022.index[i]), rotation=90)

plt.show()

4. Quelle est la ville ayant le prix moyen au mètre carré le plus élevé ?

#On garde que les prix au mètre carré supérieur à 0
dvf2022_metre_carre = dvf2022_metre_carre[dvf2022_metre_carre['Metre carre'] > 0]

ville_prix_moyen_eleve_2022 = dvf2022_metre_carre.groupby('Commune')['Valeur fonciere'].mean() / dvf2022_metre_carre.groupby('Commune')['Metre carre'].mean()

#On affiche
ville_prix_moyen_eleve_2022.idxmax()
'LA BOURDINIERE SAINT LOUP'

5. Quel est le type de bien ayant le prix moyen au mètre carré le plus élevé ?

type_de_bien_2022 = dvf2022_metre_carre.groupby('Type local')['Valeur fonciere'].mean()

#On affiche
type_de_bien_2022.idxmax()
'Local industriel. commercial ou assimilé'

6. Analyse du prix moyen au mètre carré dans les arrondissements de Paris

#Analyse du prix moyen au mètre carré dans les arrondissements de Paris

#On créé un nouveau tableau qui contient que les arrondissements de Paris
dvf2022_paris = dvf2022_metre_carre[dvf2022_metre_carre['Code departement'] == '75']

#On créé une colonne pour le numéro de l'arrondissement
dvf2022_paris['Arrondissement'] = dvf2022_paris['Commune'].str.split(' ').str[1].astype(int)

#On récupère la moyenne du prix au mètre carré par arrondissement
prix_m2_arrondissement_2022 = dvf2022_paris.groupby('Arrondissement')['Valeur fonciere'].mean() / dvf2022_paris.groupby('Arrondissement')['Metre carre'].mean()

#Plot en histogramme
prix_m2_arrondissement_2022.plot(kind='bar', x='Arrondissement', y='Prix moyen au mètre carré')

# Créer une carte centrée sur Paris
paris_map = folium.Map(location=[48.8566, 2.3522], zoom_start=12)

# Trouver le prix moyen le plus élevé
max_prix_m2 = prix_m2_arrondissement_2022.max()

geojson_url = f"https://france-geojson.gregoiredavid.fr/repo/departements/75-paris/communes-75-paris.geojson"
geojson_json = requests.get(geojson_url).json()

colors = ['#ff0000', '#ff3300', '#ff6600', '#ff9900', '#ffcc00', '#ffff00', '#e6ff00', '#ccff00', '#b3ff00', '#99ff00', '#80ff00', '#66ff00', '#4dff00', '#33ff00', '#1aff00', '#00ff00', '#00e600', '#00cc00', '#00b300', '#009900']

# Boucler sur chaque arrondissement et ajouter un polygone à la carte
for arrondissement in prix_m2_arrondissement_2022.index:
    prix_m2 = prix_m2_arrondissement_2022[arrondissement]

    # Tri de prix_m2_arrondissement_2022 par ordre croissant
    prix_m2_arrondissement_2022 = prix_m2_arrondissement_2022.sort_values()
    
    # Définir la valeur maximale de prix au mètre carré
    max_price = prix_m2_arrondissement_2022.max()
    min_price = prix_m2_arrondissement_2022.min()  

    for coord in geojson_json['features']:
        arrondissement_insee = arrondissement < 10 and "0" + str(arrondissement) or str(arrondissement)
        if coord['properties']['code'] == "751"+str(arrondissement_insee):
            color = colors[arrondissement-1]
            # Ajoute color à coord['geometry']['color']
            coord['geometry']['color'] = color

            #Calculer le centre de l'arrondissement
            center = [sum([coord['geometry']['coordinates'][0][i][1] for i in range(len(coord['geometry']['coordinates'][0]))]) / len(coord['geometry']['coordinates'][0]), sum([coord['geometry']['coordinates'][0][i][0] for i in range(len(coord['geometry']['coordinates'][0]))]) / len(coord['geometry']['coordinates'][0])]
            folium.GeoJson(data=coord['geometry'], style_function=lambda x: {"fillColor": x['geometry']['color'], "color": x['geometry']['color']}).add_to(paris_map)
            
            # Ajouter le prix moyen au mètre carré
            folium.Marker(center, popup="Arrondissement "+str(arrondissement)+" : "+str(round(prix_m2,2))+" €/m²").add_to(paris_map)
            break

# Ajouter un contrôle de couches
folium.LayerControl().add_to(paris_map)

# Afficher la carte
paris_map.save("carte_paris.html")

7. Analyse du prix moyen au mètre carré dans les arrondissements de Marseille

#Analyse du prix moyen au mètre carré dans les arrondissements de Marseille

#On créé un nouveau tableau qui contient que les arrondissements de Marseille
dvf2022_marseille = dvf2022[dvf2022['Code departement'] == '13']

#On group by sur la commune pour voir les différentes communes de Marseille
prix_m2_arrondissement_2022_marseille = dvf2022_marseille.groupby('Commune')['Valeur fonciere'].mean() / dvf2022_marseille.groupby('Commune')['Metre carre'].mean()

#On garde que les arrondissements de Marseille
prix_m2_arrondissement_2022_marseille = prix_m2_arrondissement_2022_marseille[prix_m2_arrondissement_2022_marseille.index.str.contains('MARSEILLE')]

#Plot en histogramme
prix_m2_arrondissement_2022_marseille.plot(kind='bar', x='Arrondissement', y='Prix moyen au mètre carré')
<AxesSubplot: xlabel='Commune'>

8. Analyse du prix moyen au mètre carré en fonction de la nature du bien

#Analyse du prix moyen au mètre carré en fonction de la nature du bien

prix_en_fonction_nature = dvf2022.groupby('Nature mutation')['Valeur fonciere'].mean() / dvf2022_marseille.groupby('Nature mutation')['Metre carre'].mean()

#on plot
prix_en_fonction_nature.plot(kind='bar', x='Commune', y='Prix moyen au mètre carré')
<AxesSubplot: xlabel='Nature mutation'>

9. Top 10 des villes avec un prix au mètre carré le plus élevé

#Top 10 des villes avec un prix au mètre carré le plus élevé

top_10 = dvf2022_metre_carre.groupby('Commune')['Valeur fonciere'].mean() / dvf2022_metre_carre.groupby('Commune')['Metre carre'].mean()

#On trie par ordre décroissant
top_10 = top_10.sort_values(ascending=False)

#On garde que les 10 premiers
top_10 = top_10.head(10)

#On plot
top_10.plot(kind='bar', x='Commune', y='Prix moyen au mètre carré')
<AxesSubplot: xlabel='Commune'>

10. Top 10 des communes avec un prix au mètre carré le plus élevé dans la petite couronne parisienne

#Top 10 des communes avec un prix au mètre carré le plus élevé dans la petite couronne parisienne

#On créé un nouveau tableau qui contient que les arrondissements de la petite couronne parisienne
dvf2022_petite_couronne = dvf2022_metre_carre[dvf2022_metre_carre['Code departement'].isin(['92', '93', '94'])]

#On group by sur la commune pour voir les différentes communes de la petite couronne parisienne
prix_m2_petite_couronne = dvf2022_petite_couronne.groupby('Commune')['Valeur fonciere'].mean() / dvf2022_petite_couronne.groupby('Commune')['Metre carre'].mean()

#On trie par ordre décroissant
prix_m2_petite_couronne = prix_m2_petite_couronne.sort_values(ascending=False)

#On garde que les 10 premiers
prix_m2_petite_couronne = prix_m2_petite_couronne.head(10)

#On plot
prix_m2_petite_couronne.plot(kind='bar', x='Commune', y='Prix moyen au mètre carré')
<AxesSubplot: xlabel='Commune'>